package glemipsc.instructions;

import glemipsc.Address;
import glemipsc.ErrorLevel;
import glemipsc.Function;
import glemipsc.GLEMipsC;
import glemipsc.RelativeAddress;
import glemipsc.SymbolElement;
import glemipsc.TranslatedAddress;
import glemipsc.Type;
import glemipsc.implementations.Argument;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

public class Call extends BaseInstruction implements Serializable, Instruction, Expression {
	private Function fcnt;
	private List ops;
	private Address addr;
	
	public Call(Function fcnt, List ops) {
		if (fcnt == null) {
			GLEMipsC.compiler.displayError(ErrorLevel.ERROR, "Function not defined");
		}
		this.fcnt = fcnt;
		this.ops = ops;
	}
	
	public Expression getOperand(int index) {
		return (Expression) ops.get(index);
	}
	
	public int getNumberOfOperands() {
		return -1;
	}

	public int getSize() {
		return 0;
	}

	public Type getType() {
		return fcnt.getType();
	}

	public int getImmediateValue() {
		return 0;
	}

	public boolean isImmediate() {
		return false;
	}

	public Address getStoreAddress() {
		return addr;
	}

	public int getStoredSize() {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean isStored() {
		return true;
	}

	public void setStoreAddress(Address addr) {
		this.addr = addr;
	}

	public void computeInstruction(Argument result, List<glemipsc.implementations.Instruction> instructions, Function func) {
		glemipsc.implementations.Implementation impl = GLEMipsC.compiler.environment.implementation;
		List<Argument> regs = impl.getBusyRegisters();
		// Crea il nuovo stack pointer
		Argument newStackPointer = impl.getFreeRegister();
		// Incrementa lo stack pointer
		instructions.add(impl.getAdd(newStackPointer, impl.getStackPointerRegister(), impl.getImmediate(func.getStackSize()), false));
		// Salva i registri nello stack...
		RelativeAddress addr = new RelativeAddress(newStackPointer, 0);
		Iterator<Argument> regsIter = regs.iterator();
		while (regsIter.hasNext()) {
			Argument arg = regsIter.next();
			if (arg == result)
				continue;
			instructions.add(impl.getSt(arg, addr, 4));
			//addr = new RelativeAddress(newStackPointer, addr.getValue() + impl.getRegistersBits() / 8);
			addr = new RelativeAddress(newStackPointer, addr.getValue() + 1);
		}
		// Avanza lo stack pointer
		instructions.add(impl.getAdd(newStackPointer, newStackPointer, impl.getImmediate(addr.getValue()), false));
		// Passaggio parametri
		fcnt.computeOpCode(null);
		Iterator<String> namesIter = fcnt.getParameterNames().iterator();
		int i = 0;
		while (namesIter.hasNext()) {
			SymbolElement el = fcnt.get(namesIter.next());
			((Expression)ops.get(i++)).computeInstruction(result, instructions, func);
			//instructions.add(impl.getSt(result, new TranslatedAddress(el.getStoreAddress(), newStackPointer, 4), el.getStoredSize()));
			instructions.add(impl.getSt(result, new TranslatedAddress(el.getStoreAddress(), newStackPointer, 1), el.getStoredSize()));
		}
		// Liberazione nuovo stack pointer ed aggiornamento stack pointer originale
		instructions.add(impl.getMov(impl.getStackPointerRegister(), newStackPointer));
		impl.freeRegister(newStackPointer);
		// Effettua il salto
		Argument reg = impl.getFreeRegister();
		//instructions.add(impl.getMov(reg, fcnt.getStoreAddress()));
		instructions.add(impl.getJal(fcnt.getStoreAddress()));
		impl.freeRegister(reg);
		// Preleva il risultato
		instructions.add(impl.getLd(result, new RelativeAddress(impl.getStackPointerRegister(), 0), 4));
		// Indietreggia lo stack pointer
		instructions.add(impl.getSub(impl.getStackPointerRegister(), impl.getStackPointerRegister(), impl.getImmediate(addr.getValue()), false));
		// Ripristina i registri
		addr = new RelativeAddress(impl.getStackPointerRegister(), 0);
		regsIter = regs.iterator();
		while (regsIter.hasNext()) {
			Argument arg = regsIter.next();
			if (arg == result)
				continue;
			instructions.add(impl.getLd(arg, addr, 4));
			//addr = new RelativeAddress(impl.getStackPointerRegister(), addr.getValue() + 4);
			addr = new RelativeAddress(impl.getStackPointerRegister(), addr.getValue() + 1);
		}
		// Ripristino lo stack pointer
		instructions.add(impl.getSub(impl.getStackPointerRegister(), impl.getStackPointerRegister(), impl.getImmediate(func.getStackSize()), false));
	}

}
